Skip to content

[APINotes] Add support for capturing all possible versioned APINotes without applying them #147405

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from

Conversation

artemcm
Copy link
Contributor

@artemcm artemcm commented Jul 7, 2025

Swift-versioned API notes get applied at PCM constrution time relying on '-fapinotes-swift-version=X' argument to pick the appropriate version. This change adds a new APINotes application mode with '-fswift-version-independent-apinotes' which causes all versioned API notes to get recorded into the PCM wrapped in 'SwiftVersionedAttr' instances. The expectation in this mode is that the Swift client will perform the required transformations as per the API notes on the client side, when loading the PCM, instead of them getting applied on the producer side. This will allow the same PCM to be usable by Swift clients building with different language versions.

In addition to versioned-wrapping the various existing API notes annotations which are carried in declaration attributes, this change adds a new attribute for two annotations which were previously applied directly to the declaration at the PCM producer side: 1) Type and 2) Nullability annotations with 'SwiftTypeAttr' and 'SwiftNullabilityAttr', respectively. The logic to apply these two annotations to a declaration is refactored into API.

@llvmbot llvmbot added clang Clang issues not falling into any other category clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" labels Jul 7, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 7, 2025

@llvm/pr-subscribers-clang-driver

@llvm/pr-subscribers-clang

Author: Artem Chikin (artemcm)

Changes

Swift-versioned API notes get applied at PCM constrution time relying on '-fapinotes-swift-version=X' argument to pick the appropriate version. This change adds a new APINotes application mode with '-fswift-version-independent-apinotes' which causes all versioned API notes to get recorded into the PCM wrapped in 'SwiftVersionedAttr' instances. The expectation in this mode is that the Swift client will perform the required transformations as per the API notes on the client side, when loading the PCM, instead of them getting applied on the producer side. This will allow the same PCM to be usable by Swift clients building with different language versions.

In addition to versioned-wrapping the various existing API notes annotations which are carried in declaration attributes, this change adds a new attribute for two annotations which were previously applied directly to the declaration at the PCM producer side: 1) Type and 2) Nullability annotations with 'SwiftTypeAttr' and 'SwiftNullabilityAttr', respectively. The logic to apply these two annotations to a declaration is refactored into API.


Patch is 20.60 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/147405.diff

9 Files Affected:

  • (modified) clang/include/clang/APINotes/APINotesManager.h (+9)
  • (modified) clang/include/clang/Basic/Attr.td (+20)
  • (modified) clang/include/clang/Basic/LangOptions.def (+1)
  • (modified) clang/include/clang/Driver/Options.td (+6)
  • (modified) clang/include/clang/Sema/Sema.h (+11-1)
  • (modified) clang/lib/APINotes/APINotesManager.cpp (+2-1)
  • (modified) clang/lib/Driver/ToolChains/Clang.cpp (+4)
  • (modified) clang/lib/Sema/SemaAPINotes.cpp (+143-75)
  • (added) clang/test/APINotes/versioned-version-independent.m (+36)
diff --git a/clang/include/clang/APINotes/APINotesManager.h b/clang/include/clang/APINotes/APINotesManager.h
index 98592438e90ea..772fa5faa0f87 100644
--- a/clang/include/clang/APINotes/APINotesManager.h
+++ b/clang/include/clang/APINotes/APINotesManager.h
@@ -50,6 +50,13 @@ class APINotesManager {
   /// source file from which an entity was declared.
   bool ImplicitAPINotes;
 
+  /// Whether to apply all APINotes as optionally-applied versioned
+  /// entities. This means that when building a Clang module,
+  /// we capture every note on a given decl wrapped in a SwiftVersionedAttr
+  /// (with an empty version field for unversioned notes), and have the
+  /// client apply the relevant version's notes.
+  bool VersionIndependentSwift;
+
   /// The Swift version to use when interpreting versioned API notes.
   llvm::VersionTuple SwiftVersion;
 
@@ -167,6 +174,8 @@ class APINotesManager {
 
   /// Find the API notes readers that correspond to the given source location.
   llvm::SmallVector<APINotesReader *, 2> findAPINotes(SourceLocation Loc);
+
+  bool captureVersionIndependentSwift() { return VersionIndependentSwift; }
 };
 
 } // end namespace api_notes
diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td
index 340f439a45bb9..2d6e144962119 100644
--- a/clang/include/clang/Basic/Attr.td
+++ b/clang/include/clang/Basic/Attr.td
@@ -3055,6 +3055,26 @@ def Regparm : TypeAttr {
   let ASTNode = 0;
 }
 
+def SwiftType : Attr {
+  // This attribute has no spellings as it is only ever created implicitly
+  // from API notes.
+  let Spellings = [];
+  let Args = [StringArgument<"TypeString">];
+  let SemaHandler = 0;
+  let Documentation = [InternalOnly];
+}
+
+def SwiftNullability : Attr {
+  // This attribute has no spellings as it is only ever created implicitly
+  // from API notes.
+  let Spellings = [];
+  let Args = [EnumArgument<"Kind", "Kind", /*is_string=*/false,
+                           ["non_null", "nullable", "unspecified", "nullable_result"],
+                           ["NonNull", "Nullable", "Unspecified", "NullableResult"]>];
+  let SemaHandler = 0;
+  let Documentation = [InternalOnly];
+}
+
 def SwiftAsyncName : InheritableAttr {
   let Spellings = [GNU<"swift_async_name">];
   let Args = [StringArgument<"Name">];
diff --git a/clang/include/clang/Basic/LangOptions.def b/clang/include/clang/Basic/LangOptions.def
index 72321c204ce96..e43238ba683f2 100644
--- a/clang/include/clang/Basic/LangOptions.def
+++ b/clang/include/clang/Basic/LangOptions.def
@@ -390,6 +390,7 @@ LANGOPT(RetainCommentsFromSystemHeaders, 1, 0, Compatible, "retain documentation
 
 LANGOPT(APINotes, 1, 0, NotCompatible, "use external API notes")
 LANGOPT(APINotesModules, 1, 0, NotCompatible, "use module-based external API notes")
+LANGOPT(SwiftVersionIndependentAPINotes, 1, 0, NotCompatible, "use external API notes capturing all versions")
 
 LANGOPT(SanitizeAddressFieldPadding, 2, 0, NotCompatible, "controls how aggressive is ASan "
                                                       "field padding (0: none, 1:least "
diff --git a/clang/include/clang/Driver/Options.td b/clang/include/clang/Driver/Options.td
index 54c71b066f9d4..f4e86d2bd05ca 100644
--- a/clang/include/clang/Driver/Options.td
+++ b/clang/include/clang/Driver/Options.td
@@ -1888,6 +1888,12 @@ defm apinotes_modules : BoolOption<"f", "apinotes-modules",
     NegFlag<SetFalse, [], [ClangOption], "Disable">,
     BothFlags<[], [ClangOption, CC1Option], " module-based external API notes support">>,
     Group<f_clang_Group>;
+defm swift_version_independent_apinotes : BoolOption<"f", "swift-version-independent-apinotes",
+  LangOpts<"SwiftVersionIndependentAPINotes">, DefaultFalse,
+  PosFlag<SetTrue, [], [ClangOption], "Enable">,
+  NegFlag<SetFalse, [], [ClangOption], "Disable">,
+  BothFlags<[], [ClangOption, CC1Option], " version-independent external API notes support">>,
+  Group<f_clang_Group>;
 def fapinotes_swift_version : Joined<["-"], "fapinotes-swift-version=">,
   Group<f_clang_Group>, Visibility<[ClangOption, CC1Option]>,
   MetaVarName<"<version>">,
diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h
index b281b1cfef96a..766acc6d7478f 100644
--- a/clang/include/clang/Sema/Sema.h
+++ b/clang/include/clang/Sema/Sema.h
@@ -1614,7 +1614,17 @@ class Sema final : public SemaBase {
   ///
   /// Triggered by declaration-attribute processing.
   void ProcessAPINotes(Decl *D);
-
+  /// Apply the 'Nullability:' annotation to the specified declaration
+  void ApplyNullability(Decl *D, NullabilityKind Nullability);
+  /// Apply the 'Type:' annotation to the specified declaration
+  void ApplyAPINotesType(Decl *D, StringRef TypeString);
+
+  /// Whether APINotes should be gathered for all applicable Swift language
+  /// versions, without being applied. Leaving clients of the current module
+  /// to select and apply the correct version.
+  bool captureSwiftVersionIndependentAPINotes() {
+    return APINotes.captureVersionIndependentSwift();
+  }
   ///@}
 
   //
diff --git a/clang/lib/APINotes/APINotesManager.cpp b/clang/lib/APINotes/APINotesManager.cpp
index 4dc6ffd66bd53..60868ab104c46 100644
--- a/clang/lib/APINotes/APINotesManager.cpp
+++ b/clang/lib/APINotes/APINotesManager.cpp
@@ -49,7 +49,8 @@ class PrettyStackTraceDoubleString : public llvm::PrettyStackTraceEntry {
 } // namespace
 
 APINotesManager::APINotesManager(SourceManager &SM, const LangOptions &LangOpts)
-    : SM(SM), ImplicitAPINotes(LangOpts.APINotes) {}
+    : SM(SM), ImplicitAPINotes(LangOpts.APINotes),
+      VersionIndependentSwift(LangOpts.SwiftVersionIndependentAPINotes) {}
 
 APINotesManager::~APINotesManager() {
   // Free the API notes readers.
diff --git a/clang/lib/Driver/ToolChains/Clang.cpp b/clang/lib/Driver/ToolChains/Clang.cpp
index 7b8120da558f2..1efe3bd53fbfe 100644
--- a/clang/lib/Driver/ToolChains/Clang.cpp
+++ b/clang/lib/Driver/ToolChains/Clang.cpp
@@ -7074,6 +7074,10 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
     CmdArgs.push_back("-fapinotes-modules");
   Args.AddLastArg(CmdArgs, options::OPT_fapinotes_swift_version);
 
+  if (Args.hasFlag(options::OPT_fswift_version_independent_apinotes,
+                   options::OPT_fno_swift_version_independent_apinotes, false))
+    CmdArgs.push_back("-fswift-version-independent-apinotes");
+
   // -fblocks=0 is default.
   if (Args.hasFlag(options::OPT_fblocks, options::OPT_fno_blocks,
                    TC.IsBlocksDefault()) ||
diff --git a/clang/lib/Sema/SemaAPINotes.cpp b/clang/lib/Sema/SemaAPINotes.cpp
index f21cbbbdb44ee..234295c45060e 100644
--- a/clang/lib/Sema/SemaAPINotes.cpp
+++ b/clang/lib/Sema/SemaAPINotes.cpp
@@ -52,63 +52,59 @@ static bool isIndirectPointerType(QualType Type) {
          Pointee->isMemberPointerType();
 }
 
-/// Apply nullability to the given declaration.
-static void applyNullability(Sema &S, Decl *D, NullabilityKind Nullability,
-                             VersionedInfoMetadata Metadata) {
-  if (!Metadata.IsActive)
-    return;
-
-  auto GetModified =
-      [&](Decl *D, QualType QT,
-          NullabilityKind Nullability) -> std::optional<QualType> {
-    QualType Original = QT;
-    S.CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(),
-                                            isa<ParmVarDecl>(D),
-                                            /*OverrideExisting=*/true);
-    return (QT.getTypePtr() != Original.getTypePtr()) ? std::optional(QT)
-                                                      : std::nullopt;
-  };
+static void applyAPINotesType(Sema &S, Decl *decl, StringRef typeString,
+                              VersionedInfoMetadata metadata) {
+  if (typeString.empty())
 
-  if (auto Function = dyn_cast<FunctionDecl>(D)) {
-    if (auto Modified =
-            GetModified(D, Function->getReturnType(), Nullability)) {
-      const FunctionType *FnType = Function->getType()->castAs<FunctionType>();
-      if (const FunctionProtoType *proto = dyn_cast<FunctionProtoType>(FnType))
-        Function->setType(S.Context.getFunctionType(
-            *Modified, proto->getParamTypes(), proto->getExtProtoInfo()));
-      else
-        Function->setType(
-            S.Context.getFunctionNoProtoType(*Modified, FnType->getExtInfo()));
-    }
-  } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
-    if (auto Modified = GetModified(D, Method->getReturnType(), Nullability)) {
-      Method->setReturnType(*Modified);
+    return;
 
-      // Make it a context-sensitive keyword if we can.
-      if (!isIndirectPointerType(*Modified))
-        Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
-            Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
-    }
-  } else if (auto Value = dyn_cast<ValueDecl>(D)) {
-    if (auto Modified = GetModified(D, Value->getType(), Nullability)) {
-      Value->setType(*Modified);
+  // Version-independent APINotes add "type" annotations
+  // with a versioned attribute for the client to select and apply.
+  if (S.captureSwiftVersionIndependentAPINotes()) {
+    auto *typeAttr = SwiftTypeAttr::CreateImplicit(
+        S.Context, typeString);
+    auto *versioned = SwiftVersionedAdditionAttr::CreateImplicit(
+        S.Context, metadata.Version, typeAttr, metadata.IsReplacement);
+    decl->addAttr(versioned);
+  } else {
+    if (!metadata.IsActive)
+      return;
+    S.ApplyAPINotesType(decl, typeString);
+  }
+}
 
-      // Make it a context-sensitive keyword if we can.
-      if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
-        if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified))
-          Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
-              Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
-      }
+/// Apply nullability to the given declaration.
+static void applyNullability(Sema &S, Decl *decl, NullabilityKind nullability,
+                             VersionedInfoMetadata metadata) {
+  // Version-independent APINotes add "nullability" annotations
+  // with a versioned attribute for the client to select and apply.
+  if (S.captureSwiftVersionIndependentAPINotes()) {
+    SwiftNullabilityAttr::Kind attrNullabilityKind;
+    switch (nullability) {
+      case NullabilityKind::NonNull:
+        attrNullabilityKind = SwiftNullabilityAttr::Kind::NonNull;
+        break;
+      case NullabilityKind::Nullable:
+        attrNullabilityKind = SwiftNullabilityAttr::Kind::Nullable;
+        break;
+      case NullabilityKind::Unspecified:
+        attrNullabilityKind = SwiftNullabilityAttr::Kind::Unspecified;
+        break;
+      case NullabilityKind::NullableResult:
+        attrNullabilityKind = SwiftNullabilityAttr::Kind::NullableResult;
+        break;
     }
-  } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
-    if (auto Modified = GetModified(D, Property->getType(), Nullability)) {
-      Property->setType(*Modified, Property->getTypeSourceInfo());
+    auto *nullabilityAttr = SwiftNullabilityAttr::CreateImplicit(
+        S.Context, attrNullabilityKind);
+    auto *versioned = SwiftVersionedAdditionAttr::CreateImplicit(
+        S.Context, metadata.Version, nullabilityAttr, metadata.IsReplacement);
+    decl->addAttr(versioned);
+    return;
+  } else {
+    if (!metadata.IsActive)
+      return;
 
-      // Make it a property attribute if we can.
-      if (!isIndirectPointerType(*Modified))
-        Property->setPropertyAttributes(
-            ObjCPropertyAttribute::kind_null_resettable);
-    }
+    S.ApplyNullability(decl, nullability);
   }
 }
 
@@ -361,42 +357,105 @@ static bool checkAPINotesReplacementType(Sema &S, SourceLocation Loc,
   return false;
 }
 
-/// Process API notes for a variable or property.
-static void ProcessAPINotes(Sema &S, Decl *D,
-                            const api_notes::VariableInfo &Info,
-                            VersionedInfoMetadata Metadata) {
-  // Type override.
-  if (Metadata.IsActive && !Info.getType().empty() &&
-      S.ParseTypeFromStringCallback) {
-    auto ParsedType = S.ParseTypeFromStringCallback(
-        Info.getType(), "<API Notes>", D->getLocation());
+void Sema::ApplyAPINotesType(Decl *D, StringRef TypeString) {
+  if (!TypeString.empty() &&
+      ParseTypeFromStringCallback) {
+    auto ParsedType = ParseTypeFromStringCallback(TypeString,
+                                                  "<API Notes>",
+                                                  D->getLocation());
     if (ParsedType.isUsable()) {
       QualType Type = Sema::GetTypeFromParser(ParsedType.get());
       auto TypeInfo =
-          S.Context.getTrivialTypeSourceInfo(Type, D->getLocation());
-
+        Context.getTrivialTypeSourceInfo(Type, D->getLocation());
       if (auto Var = dyn_cast<VarDecl>(D)) {
         // Make adjustments to parameter types.
         if (isa<ParmVarDecl>(Var)) {
-          Type = S.ObjC().AdjustParameterTypeForObjCAutoRefCount(
-              Type, D->getLocation(), TypeInfo);
-          Type = S.Context.getAdjustedParameterType(Type);
+          Type = ObjC().AdjustParameterTypeForObjCAutoRefCount(Type,
+                                                               D->getLocation(),
+                                                               TypeInfo);
+          Type = Context.getAdjustedParameterType(Type);
         }
 
-        if (!checkAPINotesReplacementType(S, Var->getLocation(), Var->getType(),
+        if (!checkAPINotesReplacementType(*this, Var->getLocation(),
+                                          Var->getType(),
                                           Type)) {
           Var->setType(Type);
           Var->setTypeSourceInfo(TypeInfo);
         }
-      } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
-        if (!checkAPINotesReplacementType(S, Property->getLocation(),
-                                          Property->getType(), Type))
-          Property->setType(Type, TypeInfo);
-
-      } else
+      } else if (auto property = dyn_cast<ObjCPropertyDecl>(D)) {
+        if (!checkAPINotesReplacementType(*this, property->getLocation(),
+                                          property->getType(),
+                                          Type)) {
+          property->setType(Type, TypeInfo);
+        }
+      } else {
         llvm_unreachable("API notes allowed a type on an unknown declaration");
+      }
     }
   }
+}
+
+void Sema::ApplyNullability(Decl *D, NullabilityKind Nullability) {
+  auto GetModified =
+      [&](class Decl *D, QualType QT,
+          NullabilityKind Nullability) -> std::optional<QualType> {
+    QualType Original = QT;
+    CheckImplicitNullabilityTypeSpecifier(QT, Nullability, D->getLocation(),
+                                          isa<ParmVarDecl>(D),
+                                            /*OverrideExisting=*/true);
+    return (QT.getTypePtr() != Original.getTypePtr()) ? std::optional(QT)
+                                                      : std::nullopt;
+  };
+
+  if (auto Function = dyn_cast<FunctionDecl>(D)) {
+    if (auto Modified =
+            GetModified(D, Function->getReturnType(), Nullability)) {
+      const FunctionType *FnType = Function->getType()->castAs<FunctionType>();
+      if (const FunctionProtoType *proto = dyn_cast<FunctionProtoType>(FnType))
+        Function->setType(Context.getFunctionType(
+            *Modified, proto->getParamTypes(), proto->getExtProtoInfo()));
+      else
+        Function->setType(
+            Context.getFunctionNoProtoType(*Modified, FnType->getExtInfo()));
+    }
+  } else if (auto Method = dyn_cast<ObjCMethodDecl>(D)) {
+    if (auto Modified = GetModified(D, Method->getReturnType(), Nullability)) {
+      Method->setReturnType(*Modified);
+
+      // Make it a context-sensitive keyword if we can.
+      if (!isIndirectPointerType(*Modified))
+        Method->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
+            Method->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
+    }
+  } else if (auto Value = dyn_cast<ValueDecl>(D)) {
+    if (auto Modified = GetModified(D, Value->getType(), Nullability)) {
+      Value->setType(*Modified);
+
+      // Make it a context-sensitive keyword if we can.
+      if (auto Parm = dyn_cast<ParmVarDecl>(D)) {
+        if (Parm->isObjCMethodParameter() && !isIndirectPointerType(*Modified))
+          Parm->setObjCDeclQualifier(Decl::ObjCDeclQualifier(
+              Parm->getObjCDeclQualifier() | Decl::OBJC_TQ_CSNullability));
+      }
+    }
+  } else if (auto Property = dyn_cast<ObjCPropertyDecl>(D)) {
+    if (auto Modified = GetModified(D, Property->getType(), Nullability)) {
+      Property->setType(*Modified, Property->getTypeSourceInfo());
+
+      // Make it a property attribute if we can.
+      if (!isIndirectPointerType(*Modified))
+        Property->setPropertyAttributes(
+            ObjCPropertyAttribute::kind_null_resettable);
+    }
+  }
+}
+
+/// Process API notes for a variable or property.
+static void ProcessAPINotes(Sema &S, Decl *D,
+                            const api_notes::VariableInfo &Info,
+                            VersionedInfoMetadata Metadata) {
+  // Type override.
+  applyAPINotesType(S, D, Info.getType(), Metadata);
 
   // Nullability.
   if (auto Nullability = Info.getNullability())
@@ -814,7 +873,8 @@ static void ProcessVersionedAPINotes(
     Sema &S, SpecificDecl *D,
     const api_notes::APINotesReader::VersionedInfo<SpecificInfo> Info) {
 
-  maybeAttachUnversionedSwiftName(S, D, Info);
+  if (!S.captureSwiftVersionIndependentAPINotes())
+      maybeAttachUnversionedSwiftName(S, D, Info);
 
   unsigned Selected = Info.getSelected().value_or(Info.size());
 
@@ -824,10 +884,18 @@ static void ProcessVersionedAPINotes(
     std::tie(Version, InfoSlice) = Info[i];
     auto Active = (i == Selected) ? IsActive_t::Active : IsActive_t::Inactive;
     auto Replacement = IsSubstitution_t::Original;
-    if (Active == IsActive_t::Inactive && Version.empty()) {
+
+    // When collection all APINotes as version-independent,
+    // capture all as inactive and defer to the client select the
+    // right one.
+    if (S.captureSwiftVersionIndependentAPINotes()) {
+      Active = IsActive_t::Inactive;
+      Replacement = IsSubstitution_t::Original;
+    } else if (Active == IsActive_t::Inactive && Version.empty()) {
       Replacement = IsSubstitution_t::Replacement;
       Version = Info[Selected].first;
     }
+
     ProcessAPINotes(S, D, InfoSlice,
                     VersionedInfoMetadata(Version, Active, Replacement));
   }
diff --git a/clang/test/APINotes/versioned-version-independent.m b/clang/test/APINotes/versioned-version-independent.m
new file mode 100644
index 0000000000000..da8b34a1d9ba3
--- /dev/null
+++ b/clang/test/APINotes/versioned-version-independent.m
@@ -0,0 +1,36 @@
+// RUN: rm -rf %t && mkdir -p %t
+
+// Build and check the module file in version-independent mode.
+// RUN: %clang_cc1 -fswift-version-independent-apinotes -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -fsyntax-only -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s
+// RUN: %clang_cc1 -fswift-version-independent-apinotes -fmodules -fblocks -fimplicit-module-maps -fmodules-cache-path=%t/ModulesCache/Versioned -fdisable-module-hash -fapinotes-modules -I %S/Inputs/Headers -F %S/Inputs/Frameworks %s -ast-dump -ast-dump-filter 'DUMP' &> %t/VersionedKit_AST_Dump.txt
+// RUN: cat %t/VersionedKit_AST_Dump.txt | FileCheck -check-prefix=CHECK-VERSIONED-DUMP %s
+
+#import <VersionedKit/VersionedKit.h>
+
+// CHECK-VERSIONED-DUMP-LABEL: Dumping moveToPointDUMP
+// CHECK-VERSIONED-DUMP: SwiftNameAttr {{.+}} "moveTo(x:y:)"
+// CHECK-VERSIONED-DUMP-NEXT: SwiftVersionedAdditionAttr {{.+}} Implicit 3.0
+// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} <<invalid sloc>> "moveTo(a:b:)"
+
+// CHECK-VERSIONED-DUMP-LABEL: Dumping unversionedRenameDUMP
+// CHECK-VERSIONED-DUMP: SwiftNameAttr {{.+}} "unversionedRename_HEADER()"
+// CHECK-VERSIONED-DUMP-NEXT: SwiftVersionedAdditionAttr {{.+}} Implicit 0
+// CHECK-VERSIONED-DUMP-NEXT: SwiftNameAttr {{.+}} "unversionedRename_NOTES()"
+
+// CHECK-VERSIONED-DUMP-LABEL: Dumping TestGenericDUMP
+// CHECK-VERSIONED-DUMP: SwiftVersionedAdditionAttr {{.+}} Implicit 3.0
+// CHECK-VERSIONED-DUMP-NEXT: SwiftImportAsNonGenericAttr {{.+}} <<invalid sloc>>
+
+// CHECK-VERSIONED-DUMP:  Swif...
[truncated]

Copy link

github-actions bot commented Jul 7, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

…without applying them

Swift-versioned API notes get applied at PCM constrution time relying on '-fapinotes-swift-version=X' argument to pick the appropriate version. This change adds a new APINotes application mode with '-fswift-version-independent-apinotes' which causes *all* versioned API notes to get recorded into the PCM wrapped in 'SwiftVersionedAttr' instances. The expectation in this mode is that the Swift client will perform the required transformations as per the API notes on the client side, when loading the PCM, instead of them getting applied on the producer side. This will allow the same PCM to be usable by Swift clients building with different language versions.

In addition to versioned-wrapping the various existing API notes annotations which are carried in declaration attributes, this change adds a new attribute for two annotations which were previously applied directly to the declaration at the PCM producer side: 1) Type and 2) Nullability annotations with 'SwiftTypeAttr' and 'SwiftNullabilityAttr', respectively. The logic to apply these two annotations to a declaration is refactored into API.
@artemcm artemcm force-pushed the artemcm/VersionIndependentVersionedAPINotes branch from ac1f2ee to 1e1b8ed Compare July 7, 2025 21:48
@egorzhdan egorzhdan requested review from compnerd and egorzhdan July 8, 2025 11:19
Copy link
Contributor

@egorzhdan egorzhdan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clang:driver 'clang' and 'clang++' user-facing binaries. Not 'clang-cl' clang:frontend Language frontend issues, e.g. anything involving "Sema" clang Clang issues not falling into any other category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants